using System;
using System.Collections;
using System.IO;
using System.Net;
using System.Net.Mail;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
using System.Xml.Serialization;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web;
using System.Collections.Specialized;

namespace CommonLibrary
{
	public static class ControlTools
	{
		/// <summary>
		/// Find the value of a child control
		/// </summary>
		/// <param name="control"></param>
		/// <param name="childControlId"></param>
		/// <returns></returns>
		public static string FindChildControlValue(Control control, string childControlId)
		{
			string returnValue = string.Empty;

			TextBox childTextbox = control.FindControl(childControlId) as TextBox;
			if (childTextbox != null)
			{
				returnValue = childTextbox.Text;
			}

			return returnValue;
		}
	}

	/// <summary>
	/// Helper class to assist serialization
	/// </summary>
	public static class SerializationTools
	{
		#region Public Methods

		/// <summary>
		/// Serialize an object to file
		/// </summary>
		/// <param name="objectToSerialize"></param>
		/// <param name="filePath">The path to the file</param>
		/// <param name="overwriteIfExists">If true, this method will overwrite an existing version of filePath</param>
		public static void SerializeToFile(object objectToSerialize, string filePath, bool overwriteIfExists)
		{
			try
			{
				FileInfo file = new FileInfo(filePath);
				if (file.Exists && !overwriteIfExists)
				{
					throw new Exception(string.Format("Cannot overwrite existing file, overwriteIfExists is false"));
				}
				else
				{					
					using (TextWriter WriteFileStream = new StreamWriter(filePath))
					{
						XmlSerializer SerializerObj = new XmlSerializer(objectToSerialize.GetType());
						SerializerObj.Serialize(WriteFileStream, objectToSerialize);
					}
				}
			}
			catch (Exception ex)
			{
				
				throw new Exception("Unable to serialize object. See inner exception for reason", ex);
			}
		}

		/// <summary>
		/// Load a serialized object from file
		/// </summary>
		/// <typeparam name="T">The type of the object to deserialize</typeparam>
		/// <param name="filePath">Path of the file</param>
		/// <returns></returns>
		public static T DeserializeFromFile<T>(string filePath)
		{
			try
			{
				using(TextReader reader = new StreamReader(filePath))
				{
					XmlSerializer serializer = new XmlSerializer(typeof (T));
					return (T)serializer.Deserialize(reader);
				}
			}
			catch(Exception ex)
			{
				throw new Exception(string.Format("Unable to deserialize from {0}, see inner exception for details", filePath), ex);
			}
		}

		#endregion
	}

	/// <summary>
	/// Helper class for string methods
	/// </summary>
	public static class StringTools
	{
		#region Public Methods

		/// <summary>
		/// Replaces most non-alpha-numeric chars including whitespace
		/// </summary>
		/// <param name="sIn"></param>
		/// <returns></returns>
		public static string StripNonAlphaNumeric(this string sIn)
		{
			return StripNonAlphaNumeric(sIn, true);
		}

		/// <summary>
		/// Replaces most non-alpha-numeric chars
		/// </summary>
		/// <param name="sIn"></param>
		/// <param name="removeWhitespace"></param>
		/// <returns></returns>
		public static string StripNonAlphaNumeric(this string sIn, bool removeWhitespace)
		{
			StringBuilder sb = new StringBuilder(sIn);
			
			//these are illegal characters - remove zem
			string stripList = ".'?\\/><$!@%^*&+,;:\"{}[]|-#";

			for (int i = 0; i < stripList.Length; i++)
			{
				sb.Replace(stripList[i], '*');
			}
			sb.Replace("*", string.Empty);
			if (removeWhitespace)
			{
				sb.Replace(" ", String.Empty);
			}
			return sb.ToString();
		}

		/// <summary>
		/// Convert a string to its base64 representation
		/// </summary>
		/// <param name="value"></param>
		/// <returns></returns>
		public static string ConvertToBase64(this string value)
		{
			return Convert.ToBase64String(Encoding.UTF8.GetBytes(value));
		} 

		/// <summary>
		/// Remove HTML tags from the string (Simplistic, just removes everything from
		/// in-between &gt; &amp; &lt; tags
		/// </summary>
		/// <param name="htmlString"></param>
		/// <returns></returns>
		public static string StripHtml(this string htmlString)
		{
			return Regex.Replace(htmlString, "<.*?>", string.Empty);
		}

		/// <summary>
		/// Converts a CamelCase string to a separate words
		/// </summary>
		/// <param name="input"></param>
		/// <returns></returns>
		public static string CamelCaseToSpaces(this string input)
		{
			if (!string.IsNullOrEmpty(input))
			{
				return Regex.Replace(input, "([A-Z])", " $1").Trim();
			}
			else
			{
				return null;
			}
		}

		#endregion
	}

	/// <summary>
	/// Helper class for working with mail
	/// </summary>
	public static class MailTools
	{
		#region Private Fields

		private static SmtpClient _client;

		#endregion

		#region Public Methods

		public static SmtpClient Client
		{
			get
			{
				if (_client == null)
				{
					_client = new SmtpClient();
				}

				return _client;
			}
		}

		public static void SendMail(string to, string from, string subject, string body)
		{
			MailMessage msg = new MailMessage(from, to);
			msg.Subject = subject;
			msg.Body = body;

			Client.Send(msg);
		}

		#endregion
	}

    /// <summary>
    /// Methods for manipulating & interacting with URLs
    /// </summary>
    public class UrlTools
    {
        #region Public Methods

        /// <summary>
        /// Remove the www off a url (will remove http:// if it is present) leaving just the domain name
        /// </summary>
        /// <param name="url"></param>
        /// <returns></returns>
        public static string StripWww(string url)
        {
            if (url.IndexOf("www.") > -1)
            {
                return url.Substring(url.IndexOf('.') + 1);
            }
            else
            {
                return url;
            }
        }

		[Obsolete()]
		public static string CreateUrl(string host, string path)
		{
			return CreateUrl(host, path, string.Empty);
		}

		[Obsolete()]
		public static string CreateUrl(string host, string path, string query)
		{
			if (host.EndsWith("/"))
			{
				host = host.Substring(0, host.Length - 1);
			}

			if (!string.IsNullOrEmpty(path) && !path.StartsWith("/"))
			{
				path = "/" + path;
			}

			if (!string.IsNullOrEmpty(query) && !query.StartsWith("?"))
			{
				query = "?" + query;
			}

			return (host + path + query).Trim();
		}

		/// <summary>
		/// Checks whether the DNS resolves for the domain
		/// </summary>
		/// <param name="url"></param>
		/// <returns>True if the domain resolves</returns>
		public static bool DomainResolves(string url)
		{
			if (string.IsNullOrEmpty(url))
			{
				throw new Exception("Cannot resolve empty url");
			}
			else if (url.Trim() == string.Empty)
			{
				throw new Exception("Cannot resolve empty url");
			}
			else if (!url.StartsWith("http"))
			{
				url = "http://" + url;
			}

			Uri uri = new Uri(url);
			try
			{
				IPHostEntry host = Dns.GetHostEntry(uri.Host);
				return (host != null);
			}
			catch (SocketException ex)
			{
				if (ex.ErrorCode == 11001)
				{
					return false;
				}
				else
				{
					throw;
				}
			}

		}

		public static void DoRedirect(string newUrl, bool isPermanant)
		{
			if (HttpContext.Current != null)
			{
				if (isPermanant)
				{
					HttpContext.Current.Response.StatusCode = 301;
					HttpContext.Current.Response.RedirectLocation = newUrl;
				}
				else
				{
					HttpContext.Current.Response.Redirect(newUrl);
				}
			}
		}

		/// <summary>
		/// Set a parameter value in a query string. If the parameter is not found in the passed in query string,
		/// it is added to the end of the query string
		/// </summary>
		/// <param name="queryString">The query string that is to be manipulated</param>
		/// <param name="paramName">The name of the parameter</param>
		/// <param name="paramValue">The value that the parameter is to be set to</param>
		/// <returns>The query string with the parameter set to the new value.</returns>
		public static string SetQueryStringParameter(string queryString, string paramName, object paramValue)
		{
			//create the regex
			//match paramname=*
			//string regex = String.Format(@"{0}=[^&]*", paramName);
			string regex = @"([&?]{0,1})" + String.Format(@"({0}=[^&]*)", paramName);

			RegexOptions options = RegexOptions.RightToLeft;
			// Querystring has parameters...
			if (Regex.IsMatch(queryString, regex, options))
			{
				queryString = Regex.Replace(queryString, regex, String.Format("$1{0}={1}", paramName, paramValue));
			}
			else
			{
				// If no querystring just return the Parameter Key/Value
				if (queryString == String.Empty)
				{
					return String.Format("{0}={1}", paramName, paramValue);
				}
				else
				{
					// Append the new parameter key/value to the end of querystring
					queryString = String.Format("{0}&{1}={2}", queryString, paramName, paramValue);
				}
			}
			return queryString;
		}

		/// <summary>
		/// Removes a parameter from a query string
		/// </summary>
		/// <param name="queryString">The query string that is to be manipulated</param>
		/// <param name="paramName">The name of the parameter that is to be removed</param>
		/// <returns>The query string with the parameter removed</returns>
		public static string RemoveQueryStringParameter(string queryString, string paramName)
		{
			if (queryString == string.Empty)
			{
				return string.Empty;
			}

			NameValueCollection queryStringCollection = QueryStringToNameValueCollection(queryString, false);
			if (queryStringCollection[paramName] != null)
			{
				queryStringCollection.Remove(paramName);
			}

			return NameValueCollectionToQueryString(queryStringCollection);
		}

		/// <summary>
		/// Gets a NameValueCollection ala QueryString with the param values automatically URL Decoded
		/// </summary>
		/// <param name="queryString"></param>
		/// <returns></returns>
		public static NameValueCollection QueryStringToNameValueCollection(string queryString)
		{
			return QueryStringToNameValueCollection(queryString, true);
		}

		public static NameValueCollection QueryStringToNameValueCollection(string queryString, bool autoDecodeValues)
		{
			NameValueCollection formalQueryString = new NameValueCollection();
			//strip string data before the question mark
			if (queryString.StartsWith("?"))
			{
				queryString = queryString.TrimStart('?');
			}

			if (queryString.Trim().Length > 0)
			{
				string[] splitQueryString = queryString.Split("&".ToCharArray());
				for (int i = 0; i < splitQueryString.Length; i++)
				{
					string[] pairs = splitQueryString[i].Split('=');
					if (pairs.Length == 2)
					{
						if (autoDecodeValues)
						{
							formalQueryString.Add(pairs[0], HttpUtility.UrlDecode(pairs[1]));
						}
						else
						{
							formalQueryString.Add(pairs[0], pairs[1]);
						}
					}
				}
			}
			return formalQueryString;
		}

		/// <summary>
		/// Converts a name value collection to a query string.
		/// Does not automatically url encode Name-Value collection values.
		/// </summary>
		/// <param name="queryStringCollection"></param>
		/// <returns>The query string without a question mark</returns>
		public static string NameValueCollectionToQueryString(NameValueCollection queryStringCollection)
		{
			StringBuilder queryString = new StringBuilder();

			foreach (string paramName in queryStringCollection.AllKeys)
			{
				if (queryString.Length > 0)
				{
					queryString.Append("&");
				}
				queryString.Append(string.Format("{0}={1}", paramName, queryStringCollection[paramName]));
			}

			return queryString.ToString();
		}

		/// <summary>
		/// Sets the value of the parameter & returns the string url
		/// </summary>
		/// <param name="url"></param>
		/// <param name="paramName"></param>
		/// <param name="paramValue"></param>
		/// <returns></returns>
		public static string SetQueryStringParameterInUrl(string url, string paramName, object paramValue)
		{
			int queryStringIndex = url.IndexOf("?");
			string path;
			string queryString;
			if (queryStringIndex >= 0 && !url.EndsWith("?"))
			{
				path = url.Substring(0, queryStringIndex);
				queryString = url.Substring(queryStringIndex + 1);
			}
			else
			{
				path = url;
				queryString = string.Empty;
			}
			return path + "?" + SetQueryStringParameter(queryString, paramName, paramValue);
		}

        #endregion
    }

    /// <summary>
    /// A HTTP request client
    /// </summary>
    public class Spider
	{
		#region Enums

		public enum RequestMethod
		{
			Post,
			Get
		}

		#endregion

		#region Private Fields

		private static long _totalData = 0;
        private static int _httpRequestTimeout = 6000;
        private static int _httpRequestMaxRedirects = 1;
		private static string _userAgentString = "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-GB; rv:1.8.1.6) Gecko/20070725 Firefox/2.0.0.6";
    	private static string _contentType = "text/html";

        #endregion

        #region Public Properties

		/// <summary>
		/// The content type to load the request as
		/// </summary>
		public static string ContentType
		{
			get { return _contentType; }
			set { _contentType = value; }
		}

        /// <summary>
        /// The total amount of data transmitted this app session
        /// </summary>
        public static long TotalData
        {
            get { return _totalData; }
            set { _totalData = value; }
        }

    	public static int HttpRequestTimeout
    	{
			get { return _httpRequestTimeout; }
			set { _httpRequestTimeout = value; }
    	}

    	public static int HttpRequestMaxRedirects
    	{
			get { return _httpRequestMaxRedirects; }
			set { _httpRequestMaxRedirects = value; }
    	}

		/// <summary>
		/// The user agent string to use for the request
		/// </summary>
    	public static string UserAgentString
    	{
			get { return _userAgentString; }
			set { _userAgentString = value; }
    	}

        #endregion

        #region Public Methods

		public static XmlReader GetXmlFromPostRequest(string url, string postData, int retry, string username, string password, bool forcePreAuthenticate)
		{
			XmlReader reader;

			try
			{
				ASCIIEncoding encoding = new ASCIIEncoding();
				byte[] data = encoding.GetBytes(postData);

				HttpWebRequest request = GenerateRequest(url, RequestMethod.Post, "application/xml", data.Length, username, password, forcePreAuthenticate);
				Stream stream = request.GetRequestStream();

				stream.Write(data, 0, data.Length);
				stream.Close();

				WebResponse response = request.GetResponse();
				XmlReaderSettings settings = new XmlReaderSettings();
				settings.ProhibitDtd = false;
				reader = XmlReader.Create(response.GetResponseStream(), settings);
			}
			catch (Exception)
			{
				if (retry > 0)
				{
					reader = GetXmlFromUrl(url, (retry - 1), username, password, forcePreAuthenticate);
				}
				else
				{
					throw;
				}
			}

			return reader;
		}

		/// <summary>
		/// Return an XmlReader from the xml document at the url
		/// </summary>
		/// <param name="url"></param>
		/// <param name="retry"></param>
		/// <param name="username"></param>
		/// <param name="password"></param>
		/// <param name="forcePreAuthenticate"></param>
		/// <returns></returns>
		public static XmlReader GetXmlFromUrl(string url, int retry, string username, string password, bool forcePreAuthenticate)
		{
			HttpWebRequest request = GenerateRequest(url, "application/xml", username, password, forcePreAuthenticate);

			XmlReader reader;

			try
			{
				WebResponse response = request.GetResponse();
				XmlReaderSettings settings = new XmlReaderSettings();
				settings.ProhibitDtd = false;
				reader = XmlReader.Create(response.GetResponseStream(), settings);
			}
			catch (Exception)
			{
				if (retry > 0)
				{
					reader = GetXmlFromUrl(url, (retry - 1), username, password, forcePreAuthenticate);
				}
				else
				{
					throw;
				}
			}
			return reader;
		}

		/// <summary>
		/// Return a url that requires authentication
		/// </summary>
		/// <param name="url">The url to load</param>
		/// <param name="retry">The number of times to retry loading on failure</param>
		/// <param name="username">Username to authenticate</param>
		/// <param name="password">Password to authenticate</param>
		/// <param name="forcePreAuthenticate"></param>
		/// <returns></returns>
		public static string GetUrl(string url, int retry, string username, string password, bool forcePreAuthenticate)
		{
			HttpWebRequest request = GenerateRequest(url, ContentType, username, password, forcePreAuthenticate);

			string content;
			StreamReader reader = null;

			try
			{
				WebResponse response = request.GetResponse();
				reader = new StreamReader(response.GetResponseStream());
				content = reader.ReadToEnd();
				reader.Close();
				_totalData += response.ContentLength;
			}
			catch (WebException ex)
			{
				if (ex.Status == WebExceptionStatus.Timeout)
				{
					if (retry > 0)
					{
						content = GetUrl(url, (retry - 1), username, password, forcePreAuthenticate);
					}
					else
					{
						throw;
					}
				}
				else
				{
					throw;
				}
			}
			finally 
			{
				if (reader != null)
				{
					reader.Close();
				}
			}

			return content;

		}

        /// <summary>
        /// Get the URL
        /// </summary>
		/// <param name="url">The url to load</param>
		/// <param name="retry">The number of times to retry loading on failure</param>
		/// <returns></returns>
        public static string GetUrl(string url, int retry)
        {
			return GetUrl(url, retry, null, null, false);
        }     
        #endregion

		#region Private Methods

		private static HttpWebRequest GenerateRequest(string url, string contentType, string username, string password, bool forcePreAuthenticate)
		{
			return GenerateRequest(url, RequestMethod.Get, contentType, 0, username, password, forcePreAuthenticate);
		}

		/// <summary>
		/// Generate the request object
		/// </summary>
		/// <param name="url"></param>
		/// <param name="requestMethod"></param>
		/// <param name="contentType"></param>
		/// <param name="contentLength"></param>
		/// <param name="username"></param>
		/// <param name="password"></param>
		/// <param name="forcePreAuthenticate"></param>
		/// <returns></returns>
		private static HttpWebRequest GenerateRequest(string url, RequestMethod requestMethod, string contentType, long contentLength, string username, string password, bool forcePreAuthenticate)
		{
			if (!url.StartsWith("http"))
			{
				url = "http://" + url;
			}

			HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
			request.Timeout = HttpRequestTimeout;
			request.MaximumAutomaticRedirections = HttpRequestMaxRedirects;
			request.AllowAutoRedirect = true;
			request.AllowWriteStreamBuffering = true;
			request.ContentLength = contentLength;
			request.KeepAlive = true;
			request.Method = requestMethod.ToString().ToUpper();
			request.UserAgent = UserAgentString;
			request.Referer = url;
			request.ContentType = contentType;
			request.Accept = contentType;
			if (!string.IsNullOrEmpty(username + password))
			{
				request.PreAuthenticate = forcePreAuthenticate;

				string auth = username + ":" + password;
				request.Headers.Add("Authorization", "Basic " + StringTools.ConvertToBase64(auth));
			}

			return request;
		}

		#endregion
	}        
    
	/// <summary>
	/// Methods for processing DateTime objects
	/// </summary>
	public static class DateTimeTools
	{
		#region Public Methods

		/// <summary>
		/// Returns a DateTime object with the month and year the same as the passed in date parameter
		/// but with the date set to 1
		/// </summary>
		/// <param name="date"></param>
		/// <returns></returns>
		public static DateTime FirstDayOfMonth(DateTime date)
		{
			return new DateTime(date.Year, date.Month, 1);
		}

		/// <summary>
		/// Returns a DateTime object with the month and year the same as the passed in date parameter
		/// but with the date set to the last day of the month
		/// </summary>
		/// <param name="date"></param>
		/// <returns></returns>
		public static DateTime LastDayOfMonth(DateTime date)
		{
			return new DateTime(date.Year, date.Month, DateTime.DaysInMonth(date.Year, date.Month));
		}

		/// <summary>
		/// Returns true if the two dates are from the same month of the same year
		/// </summary>
		/// <param name="date1"></param>
		/// <param name="date2"></param>
		/// <returns></returns>
		public static bool AreSameMonth(DateTime date1, DateTime date2)
		{
			return (date1.Month == date2.Month) && (date1.Year == date2.Year);
		}

		/// <summary>
		/// Extension method to see if the current DateTime obejct has the same month and year as
		/// the passed in DateTime object
		/// </summary>
		/// <param name="date1"></param>
		/// <param name="date2"></param>
		/// <returns></returns>
		public static bool HasSameMonth(this DateTime date1, DateTime date2)
		{
			return AreSameMonth(date1, date2);
		}

		#endregion
	}

	/// <summary>
	/// Generic class implements singleton pattern.
	/// </summary>
	/// <typeparam name="T">
	/// Reference type.
	/// </typeparam>
	public static class Singleton<T> where T : class, new()
	{
		private static T _instance;

		static Singleton()
		{
			_instance = new T();
		}

		public static T Instance
		{
			get { return _instance; }
		}
	}

}

